/**
 *    Copyright 2009-2021 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.io;

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * 为访问应用服务器内的资源提供了一个非常简单的API。
 *
 * @author Ben Gunter
 */
public abstract class VFS {

    private static final Log log = LogFactory.getLog(VFS.class);

    /**
     * 内置的实现。
     */
    public static final Class<?>[] IMPLEMENTATIONS = {JBoss6VFS.class, DefaultVFS.class};

    /**
     * 由{@link #addImplClass(Class)}添加的实现列表。
     */
    public static final List<Class<? extends VFS>> USER_IMPLEMENTATIONS = new ArrayList<>();

    /**
     * 采用静态内部类形式实现单例
     * <p>
     * 单例实例持有人。
     */
    private static class VFSHolder {
        static final VFS INSTANCE = createVFS();

        @SuppressWarnings("unchecked")
        static VFS createVFS() {
            //先尝试用户实现，然后再尝试内置实现
            List<Class<? extends VFS>> impls = new ArrayList<>();
            impls.addAll(USER_IMPLEMENTATIONS);
            impls.addAll(Arrays.asList((Class<? extends VFS>[]) IMPLEMENTATIONS));

            //尝试每个实现类，直到找到一个有效的实现类
            VFS vfs = null;
            for (int i = 0; vfs == null || !vfs.isValid(); i++) {
                Class<? extends VFS> impl = impls.get(i);
                try {
                    //无参构造创建实例
                    vfs = impl.getDeclaredConstructor().newInstance();
                    if (!vfs.isValid() && log.isDebugEnabled()) {
                        log.debug("VFS implementation " + impl.getName() + " is not valid in this environment.");
                    }
                } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
                    log.error("Failed to instantiate " + impl, e);
                    return null;
                }
            }

            if (log.isDebugEnabled()) {
                log.debug("Using VFS adapter " + vfs.getClass().getName());
            }

            return vfs;
        }
    }

    /**
     * 获取单例{@link VFS}实例。如果在当前环境中找不到{@link VFS}实现，那么这个方法将返回null。
     *
     * @return VFS的单个实例
     */
    public static VFS getInstance() {
        return VFSHolder.INSTANCE;
    }

    /**
     * 将指定的类添加到{@link VFS}实现列表中。
     * 以这种方式添加的类将按照它们添加的顺序在任何内置实现之前进行尝试。
     *
     * @param clazz 要添加的{@link VFS}实现类。
     */
    public static void addImplClass(Class<? extends VFS> clazz) {
        if (clazz != null) {
            USER_IMPLEMENTATIONS.add(clazz);
        }
    }

    /**
     * 通过名称获取类。如果没有找到该类，则返回null。
     *
     * @param className the class name
     * @return the class
     */
    protected static Class<?> getClass(String className) {
        try {
            return Thread.currentThread().getContextClassLoader().loadClass(className);
        } catch (ClassNotFoundException e) {
            if (log.isDebugEnabled()) {
                log.debug("Class not found: " + className);
            }
            return null;
        }
    }

    /**
     * 按名称和参数类型获取方法。如果没有找到该方法，则返回null。
     *
     * @param clazz          方法所属的类。
     * @param methodName     方法的名称。
     * @param parameterTypes 方法接受的参数类型。
     * @return the method
     */
    protected static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
        if (clazz == null) {
            return null;
        }

        try {
            return clazz.getMethod(methodName, parameterTypes);
        } catch (SecurityException e) {
            log.error("Security exception looking for method " + clazz.getName() + "." + methodName + ".  Cause: " + e);
            return null;
        } catch (NoSuchMethodException e) {
            log.error("Method not found " + clazz.getName() + "." + methodName + "." + methodName + ".  Cause: " + e);
            return null;
        }
    }

    /**
     * 在一个对象上调用一个方法并返回它返回的任何东西。
     *
     * @param <T>        泛型类型
     * @param method     要调用的方法。
     * @param object     调用该方法的实例或类(用于静态方法)。
     * @param parameters 传递给方法的参数。
     * @return 无论方法返回什么。
     * @throws IOException      If I/O errors occur
     * @throws RuntimeException If anything else goes wrong
     */
    @SuppressWarnings("unchecked")
    protected static <T> T invoke(Method method, Object object, Object... parameters) throws IOException, RuntimeException {
        try {
            return (T) method.invoke(object, parameters);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            if (e.getTargetException() instanceof IOException) {
                throw (IOException) e.getTargetException();
            } else {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 从上下文类加载器中获取在指定路径上找到的所有资源的{@link URL}列表。
     *
     * @param path 资源路径。
     * @return 一个{@link URL}的列表，由{@link ClassLoader#getResources(String)}返回。
     * @throws IOException If I/O errors occur
     */
    protected static List<URL> getResources(String path) throws IOException {
        return path == null ? Collections.emptyList() : Collections.list(Thread.currentThread().getContextClassLoader().getResources(path));
    }

    /**
     * 如果{@link VFS}实现对当前环境有效则返回true。
     *
     * @return true, if is valid
     */
    public abstract boolean isValid();

    /**
     * 递归列出所有资源的完整资源路径，这些资源是由URL标识的资源的子资源。
     *
     * @param url     标识要列出的资源的URL。
     * @param forPath 由URL标识的资源的路径。通常，这是传递给{@link #getResources(String)}以获取资源URL的值。
     * @return 包含子资源名称的列表。
     * @throws IOException If I/O errors occur
     */
    protected abstract List<String> list(URL url, String forPath) throws IOException;

    /**
     * 递归列出所有资源的完整资源路径，这些资源是在指定路径上找到的所有资源的子资源。
     *
     * @param path 要列出的资源的路径。
     * @return 包含子资源名称的列表。
     * @throws IOException If I/O errors occur
     */
    public List<String> list(String path) throws IOException {
        List<String> names = new ArrayList<>();
        for (URL url : getResources(path)) {
            names.addAll(list(url, path));
        }
        return names;
    }
}
